/**
 * OSAmI: Open Source Ambient Intelligence
 * Copyright (C) 2010 UJF.
 * Contact: osami-general@osami-commons.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 */

package org.osami.gateway.mediator.alarm.internal;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.osami.gateway.alarm.IAlarm;
import org.osami.gateway.profile.IProfileService;
import org.osgi.service.log.LogService;

import fr.liglab.adele.cilia.Data;

public class ProcessorAlarmTrigger {

    public static final String AVERAGE_VALUE_KEY = "average-value";

    public static final String AVERAGE_TYPE_KEY = "average-type";

    public static final String AVERAGE_LOCATION_KEY = "average-location";

    private IProfileService heatingProfile;

    private IProfileService luminosityProfile;

    /**
     * Logger, injected by iPOJO.
     */
    private LogService logService;

    public void setHeatingProfile(IProfileService heatingProfile) {
        this.heatingProfile = heatingProfile;
    }

    public void setLuminosityProfile(IProfileService luminosityProfile) {
        this.luminosityProfile = luminosityProfile;
    }

    public void setLogService(LogService logService) {
        this.logService = logService;
    }

    /**
     * Check the temperature.
     */
    public List<Data> process(List<Data> input) {
        List<Data> result = new ArrayList<Data>();

        if (input != null) {

            // Process all the inputs
            for (Data data : input) {

                // Extract values from the Data instance
                String type = (String) data.getProperty(AVERAGE_TYPE_KEY);
                Double average = (Double) data.getProperty(AVERAGE_VALUE_KEY);
                String location = (String) data.getProperty(AVERAGE_LOCATION_KEY);

                // Ignore the Data if anything is missing
                if (type == null) {
                    logService.log(LogService.LOG_WARNING, AVERAGE_TYPE_KEY + " field is null, ignoring data.");
                    continue;
                }
                if (average == null) {
                    logService.log(LogService.LOG_WARNING, AVERAGE_VALUE_KEY + " field is null, ignoring data.");
                    continue;
                }
                if (location == null) {
                    logService.log(LogService.LOG_WARNING, AVERAGE_LOCATION_KEY + " field is null, ignoring data.");
                    continue;
                }

                // Process the data accordingly to its type (temperature / light)
                if ("temperature".equals(type)) {
                    Data alarm = handleTemperature(average, location);
                    if (alarm != null) {
                        result.add(alarm);
                    }
                } else if ("light".equals(type)) {
                    Data alarm = handleLuminosity(average, location);
                    if (alarm != null) {
                        result.add(alarm);
                    }
                } else if ("humidity".equals(type)) {
                	// Silently ignore humidity
                	// TODO something ???
                } else {
                    // Unknown type: ignore the data
                    logService.log(LogService.LOG_WARNING, "Data type is '" + type + "': unknown type, ignoring the data.");
                }
            }
        }

        return result;
    }

    private Data handleTemperature(Double average, String location) {

        // Mimic what the Drools heating rule do
        double target = Double.valueOf(heatingProfile.getParameter("targetTemperature"));
        double delta = Double.valueOf(heatingProfile.getParameter("targetDelta"));
        double lowThreshold = target - delta;
        double highThreshold = target + delta;

        if (average < lowThreshold) {

            // Temperature below the low threshold
            long timestamp = System.currentTimeMillis();
            String identifier = "HEAT-BLW-" + timestamp;
            String message = "Average room temperature (" + average  + ") is inferior to "
                             + lowThreshold + ": activating heaters.";
            String type = "HEATING_BELOW_THRESHOLD";

            Data alarm = createAlarm(IAlarm.Severity.WARNING, timestamp, identifier, message, type, location, lowThreshold, average);
            // Add specific fields
            alarm.setProperty("unit", "celsius");

            return alarm;
        } else if (average > highThreshold) {

            // Temperature above the high threshold
            long timestamp = System.currentTimeMillis();
            String identifier = "HEAT-OVR-" + timestamp;
            String message = "Average room temperature (" + average  + ") is superior to "
                             + highThreshold + ": shutting down heaters.";
            String type = "HEATING_ABOVE_THRESHOLD";

            Data alarm = createAlarm(IAlarm.Severity.WARNING, timestamp, identifier, message, type, location, highThreshold, average);
            // Add specific fields
            alarm.setProperty("unit", "celsius");

            return alarm;
        }
        // else temperature is normal : nothing to do
        return null;
    }

    private Data handleLuminosity(Double average, String location) {

        // Mimic what the Drools luminosity rule do
        double lowThreshold = Double.valueOf(luminosityProfile.getParameter("bottomValue"));
        double highThreshold = Double.valueOf(luminosityProfile.getParameter("topValue"));
        String plugId = luminosityProfile.getParameter("plugId");

        if (average < lowThreshold) {

            // Temperature below the low threshold
            long timestamp = System.currentTimeMillis();
            String identifier = "LUM-BLW-" + timestamp;
            String message = "";
            String type = "LUMINOSITY_BELOW_THRESHOLD";

            Data alarm = createAlarm(IAlarm.Severity.INFO, timestamp, identifier, message, type, location, lowThreshold, average);
            // Add specific fields
            if (plugId != null) {
                alarm.setProperty("target", plugId);
            }

            return alarm;
        } else if (average > highThreshold) {

            // Temperature above the high threshold
            long timestamp = System.currentTimeMillis();
            String identifier = "LUM-ABV-" + timestamp;
            String message = "";
            String type = "LUMINOSITY_ABOVE_THRESHOLD";

            Data alarm = createAlarm(IAlarm.Severity.INFO, timestamp, identifier, message, type, location, highThreshold, average);
            // Add specific fields
            if (plugId != null) {
                alarm.setProperty("target", plugId);
            }

            return alarm;
        }
        // else luminosity is normal : nothing to do
        return null;
    }

    private Data createAlarm(IAlarm.Severity severity,
                             long timestamp,
                             String identifier,
                             String message,
                             String type,
                             String location,
                             double threshold,
                             Double average) {

        Data alarm = new Data(null);
        alarm.setProperty(IAlarm.ID_PROPERTY, identifier);
        alarm.setProperty(IAlarm.DATE_PROPERTY, new Date(timestamp));
        alarm.setProperty(IAlarm.MESSAGE_PROPERTY, message);
        alarm.setProperty(IAlarm.SEVERITY_PROPERTY, severity.name());

        alarm.setProperty("source", location);
        alarm.setProperty("type", type);
        alarm.setProperty("threshold", threshold);
        alarm.setProperty("value", average);

        return alarm;
    }
}
